
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;

/**
 * Created by haitao on 7/28/17.
 */
public class CppToProtobuf {

	public static final String PATHNAME_THOSTFTDCUSERAPISTRUCT = "../ThostFtdcUserApiStruct.h";
	public static final String PATHNAME_THOSTFTDCUSERAPIDATATYPE = "../ThostFtdcUserApiDataType.h";

	public static final Map<String, String> CPP_PROTOBUF_TYPE_MAP = new HashMap<>();

	static {
		CPP_PROTOBUF_TYPE_MAP.put("char", "string");
		CPP_PROTOBUF_TYPE_MAP.put("int", "int32");
		CPP_PROTOBUF_TYPE_MAP.put("double", "double");
		CPP_PROTOBUF_TYPE_MAP.put("short", "int32");
	}

	public static void main(String[] args) throws IOException {
		Map<String, FieldInfo> stringFieldInfoMap = cppToProtoFieldInfo(PATHNAME_THOSTFTDCUSERAPIDATATYPE);
		stringFieldInfoMap.forEach((x,y) -> System.out.println(x + ":" + y));

		List<ObjectInfo> objectInfos = cppToProtoStructInfo(PATHNAME_THOSTFTDCUSERAPISTRUCT);
		for (ObjectInfo objectInfo : objectInfos) {
			List<ObjectMmbVarInfo> mmbVarInfoList = objectInfo.getMmbVarInfoList();
			for (ObjectMmbVarInfo objectMmbVarInfo : mmbVarInfoList) {
				FieldInfo fieldInfo = stringFieldInfoMap.get(objectMmbVarInfo.getType());
				objectMmbVarInfo.setRealType(fieldInfo.getProtoType());
			}
		}

		objectInfos.forEach( x -> System.out.println(x));
	}

	public static List<ObjectInfo> cppToProtoStructInfo(String pathname) throws IOException {
		String str =
				"///信息分发\n" +
						"struct CThostFtdcDisseminationField\n" +
						"{\n" +
						"    ///序列系列号\n" +
						"    TThostFtdcSequenceSeriesType    SequenceSeries;\n" +
						"    ///序列号\n" +
						"    TThostFtdcSequenceNoType    SequenceNo;\n" +
						"};\n" +
						"\n"+
						"///二级代理操作员银期权限查询\n" +
						"struct CThostFtdcQrySecAgentACIDMapField\n" +
						"{\n" +
						"    ///经纪公司代码\n" +
						"    TThostFtdcBrokerIDType  BrokerID;\n" +
						"    ///用户代码\n" +
						"    TThostFtdcUserIDType    UserID;\n" +
						"    ///资金账户\n" +
						"    TThostFtdcAccountIDType AccountID;\n" +
						"    ///币种\n" +
						"    TThostFtdcCurrencyIDType    CurrencyID;\n" +
						"};\n";
//		List<String> lines = Lists.newArrayList(str.split("\n"));
		List<String> lines = readLines(new File(pathname), "gbk");

		while(true){
			String line = lines.get(0);
			if(!line.startsWith("#include")) {
				lines.remove(0);
				continue;
			}

			lines.remove(0);
			break;
		}

		List<ObjectInfo> parse = new CppStructToProto(lines).parse();
		return parse;
	}

	private static Map<String, FieldInfo> cppToProtoFieldInfo(String pathname) throws IOException {
		String str =
				"typedef char TThostFtdcExchangePropertyType;\n" +
						"typedef char TThostFtdcDateType[9];\n" +
						"typedef int TThostFtdcIPPortType;\n" +
						"#define THOST_FTDC_ICT_EID '0'\n" +
						"typedef double TThostFtdcMoneyType;\n" +
						"typedef char TThostFtdcBusinessScopeType[1001];\n" +
						"typedef short TThostFtdcSequenceSeriesType;\n";

//		List<String> lines = Lists.newArrayList(str.split("\n"));
		List<String> lines = readLines(new File(pathname), "gbk");

		Map<String, FieldInfo> fieldInfoMap = parseCppToProtoFieldInfo(lines);
		return fieldInfoMap;
	}

	public static class ObjectInfo {
		String name;
		String desc;
		List<ObjectMmbVarInfo> mmbVarInfoList;

		public ObjectInfo(String name, String desc, List<ObjectMmbVarInfo> mmbVarInfoList) {
			this.name = name;
			this.desc = desc;
			this.mmbVarInfoList = mmbVarInfoList;
		}

		public String getName() {
			return name;
		}

		public String getDesc() {
			return desc;
		}

		public List<ObjectMmbVarInfo> getMmbVarInfoList() {
			return mmbVarInfoList;
		}

		@Override
		public String toString() {

			StringBuilder sb = new StringBuilder("\n");
			for (ObjectMmbVarInfo objectMmbVarInfo : mmbVarInfoList) {
				sb.append('\t').append(objectMmbVarInfo).append('\n');
			}

			return "{" +
					"name='" + name + '\'' +
					", desc='" + desc + '\'' +
					", mmbVarInfoList=["
						+ sb.toString() +
					"]}";
		}
	}

	public static class ObjectMmbVarInfo {
		String type;
		String realType;
		String name;
		String desc;

		public ObjectMmbVarInfo(String type, String name, String desc) {
			this.type = type;
			this.name = name;
			this.desc = desc;
		}

		public String getType() {
			return type;
		}

		public String getName() {
			return name;
		}

		public String getDesc() {
			return desc;
		}

		public String getRealType() {
			return realType;
		}

		public void setRealType(String realType) {
			this.realType = realType;
		}

		@Override
		public String toString() {
			return "{" +
					"realType='" + realType + '\'' +
					", name='" + name + '\'' +
					", desc='" + desc + '\'' +
					", type='" + type + '\'' +
					'}';
		}
	}

	public static class CppStructToProto {

		Iterator<String> iterator = null;
		public CppStructToProto(List<String> lines) {
			this.iterator = lines.iterator();
		}

		public List<ObjectInfo> parse() {
			List<ObjectInfo> objectInfoList = new ArrayList<>();

			String line;
			while (true) {
				line = getLine("///");
				if(ie(line)) break;

				ObjectInfo objectInfo = parseObjInfo(line);
				objectInfoList.add(objectInfo);
			}

			return objectInfoList;
		}

		private ObjectInfo parseObjInfo(String cl) {
			ObjectInfo objectInfo = null;
			String line = null;
			// struct 注释标识符
			line = getLine("///", cl);
			String objDesc = line.substring("///".length());

			line = getLine("struct");
			String objName = te(line.substring("struct".length()));

			line = getLine("{");
			List<ObjectMmbVarInfo> objectMmbVarInfoList = new ArrayList<>();

			while (!(line = getOneLine()).startsWith("}")) {
				ObjectMmbVarInfo objectMmbVarInfo = parseMmbField(line);
				objectMmbVarInfoList.add(objectMmbVarInfo);
			}

			objectInfo = new ObjectInfo(objName, objDesc, objectMmbVarInfoList);
			return objectInfo;
		}

		private ObjectMmbVarInfo parseMmbField(String cl) {
			String line;
			line = getLine("///", cl);
			String mmbVarDesc = line.substring("///".length());

			line = getLine();
			String[] ms = getNotEmptyString(line.split("[ \t]"));
			String mmbVarType = ms[0];
			String mmbVarName = ms[1];
			mmbVarName = mmbVarName.substring(0, mmbVarName.length()-1);

			ObjectMmbVarInfo objectMmbVarInfo = new ObjectMmbVarInfo(mmbVarType, mmbVarName, mmbVarDesc);
			return objectMmbVarInfo;
		}

		String[] getNotEmptyString(String[] strings) {
			List<String> list = new ArrayList<>();
			for (String string : strings) {
				string = te(string);
				if(nm(string)) {
					list.add(string);
				}
			}
			return list.toArray(new String[0]);
		}

		private String getOneLine() {
			return iterator.hasNext() ? te(iterator.next()) : EMPTY;
		}

		private String getLine(String regex) {
			return getLine(regex, null);
		}

		private String getLine(String prefix, String cl) {
			return getLine(prefix, cl, str -> str.startsWith(prefix));
		}

		private String getLine() {
			return getLine(null ,null , str ->
					nm(str) && Character.isLetter(str.charAt(0)));
		}

		private String getLine(String prefix, String cl, Condition condition) {
			Iterator<String> ite = this.iterator;
			String line = EMPTY;

			try {
				cl = te(cl);
				if (nm(cl) && cl.startsWith(prefix)) {
					return cl;
				}

				while(ite.hasNext()) {
					line = te(ite.next());
					if(condition.eval(line)) {
						break;
					} else {
						line = EMPTY;
					}
				}

				return line;
			} finally {
//				System.out.println(cl + " " + line);
			}
		}
	}

	public static final String EMPTY = "";
	public static boolean nm(String str) {
		return !ie(str);
	}

	public static boolean ie(String str) {
		return str == null || str.length() == 0;
	}

	public static String te(String str) {
		return str == null?"":str.trim();
	}

	public static interface Condition {
		boolean eval(String str);
	}

	public static class FieldInfo {
		String type = null;
		String fieldName = null;
		String charSize = null;
		String protoType = null;

		public FieldInfo(String type, String fieldName, String charSize, String protoType) {
			this.type = type;
			this.fieldName = fieldName;
			this.charSize = charSize;
			this.protoType = protoType;
		}

		public String getType() {
			return type;
		}

		public String getFieldName() {
			return fieldName;
		}

		public String getCharSize() {
			return charSize;
		}

		public String getProtoType() {
			return protoType;
		}

		@Override
		public String toString() {
			return "{" +
					"type='" + type + '\'' +
					", fieldName='" + fieldName + '\'' +
					", protoType='" + protoType + '\'' +
					", charSize='" + charSize + '\'' +
					'}';
		}
	}

	private static Map<String, FieldInfo> parseCppToProtoFieldInfo(List<String> lines) {
		Map<String, FieldInfo> fieldInfoMap = new HashMap<String, FieldInfo>();
		for (String line : lines) {
			if(line.indexOf("typedef")==0) {
				line = line.substring("typedef".length());
				line = te(line);
				for (String n : new String[]{"char", "int", "double", "short"}) {
					if(line.indexOf(n)==0) {
						String others = te(line.substring(n.length()));
						String type = n;
						String fieldName = null;
						String charSize = null;

						if(others.indexOf('[') >= 0) {
							charSize = others.substring(others.indexOf('[')+1, others.indexOf(']'));
							fieldName = te(others.substring(0, others.indexOf('[')));
						} else {
							fieldName = te(others.substring(0, others.indexOf(';')));
						}

						fieldInfoMap.put(fieldName, new FieldInfo(n, fieldName, charSize, CPP_PROTOBUF_TYPE_MAP.get(n)));
					}
				}
			}
		}
		return fieldInfoMap;
	}


	public static List<String> readLines(File file, String encoding) throws IOException {
		try(InputStream in = new FileInputStream(file)) {
			return readLines(in, Charset.forName(encoding));
		}
	}

	public static List<String> readLines(InputStream input, Charset encoding) throws IOException {
		InputStreamReader reader = new InputStreamReader(input, encoding);
		return readLines(reader);
	}

	public static List<String> readLines(Reader input) throws IOException {
		BufferedReader reader = toBufferedReader(input);
		List<String> list = new ArrayList<String>();
		String line = reader.readLine();
		while (line != null) {
			list.add(line);
			line = reader.readLine();
		}
		return list;
	}

	public static BufferedReader toBufferedReader(Reader reader) {
		return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
	}
}
